<!DOCTYPE html>
<html lang="en">
<head>
    
    <meta charset="utf-8">
    <title>engines/specialEngine.js - Documentation</title>
    
    
    <script src="scripts/prettify/prettify.js"></script>
    <script src="scripts/prettify/lang-css.js"></script>
    <!--[if lt IE 9]>
      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <link type="text/css" rel="stylesheet" href="styles/prettify.css">
    <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
    <script src="scripts/nav.js" defer></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>

<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
  <div class="navicon"></div>
</label>

<label for="nav-trigger" class="overlay"></label>

<nav >
    
    <input type="text" id="nav-search" placeholder="Search" />
    
    <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="openBSE.BrowserNotSupportError.html">BrowserNotSupportError</a></li><li><a href="openBSE.Contextmenu.html">Contextmenu</a><ul class='methods'><li data-type='method' style='display: none;'><a href="openBSE.Contextmenu.html#closeContextmenu">closeContextmenu</a></li><li data-type='method' style='display: none;'><a href="openBSE.Contextmenu.html#getBulletScreenEvent">getBulletScreenEvent</a></li><li data-type='method' style='display: none;'><a href="openBSE.Contextmenu.html#getContextmenuState">getContextmenuState</a></li></ul></li><li><a href="openBSE.GeneralEngine.html">GeneralEngine</a><ul class='methods'><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#add">add</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#bind">bind</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#cleanBuffer">cleanBuffer</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#cleanScreen">cleanScreen</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#pause">pause</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#play">play</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#playAllBulletScreens">playAllBulletScreens</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#stop">stop</a></li><li data-type='method' style='display: none;'><a href="openBSE.GeneralEngine.html#unbind">unbind</a></li></ul></li></ul><h3>Events</h3><ul><li><a href="openBSE.GeneralEngine.html#event:click">click</a></li><li><a href="openBSE.GeneralEngine.html#event:contextmenu">contextmenu</a></li><li><a href="openBSE.GeneralEngine.html#event:mouseenter">mouseenter</a></li><li><a href="openBSE.GeneralEngine.html#event:mouseleave">mouseleave</a></li></ul><h3>Namespaces</h3><ul><li><a href="openBSE.html">openBSE</a><ul class='methods'><li data-type='method' style='display: none;'><a href="openBSE.html#.getVersion">getVersion</a></li></ul></li></ul>
</nav>

<div id="main">
    
    <h1 class="page-title">engines/specialEngine.js</h1>
    

    



    
    <section>
        <article>
            <pre class="prettyprint source linenums"><code>import LinkedList from '@magic-power-research/linked-list-js'
import RenderersFactory from '../renderers/renderersFactory'
import Helper from '../lib/helper'
import Resources from '../lib/resources'
import Interpreter from '../lib/JS-Interpreter/interpreter'
import { requestAnimationFrame, cancelAnimationFrame, performance_now } from '../lib/requestAnimationFrame'

class SpecialEngine {
    constructor(element, options, renderMode = 'canvas') {
        //变量初始化
        /**
         * 开始时间
         * @private @type {number}
         */
        let _startTime;
        /**
         * 暂停时间
         * @private @type {number}
         */
        let _pauseTime = 0;
        /**
         * 弹幕缓冲区
         * @private @type {LinkedList}
         */
        let _bulletScreenBuffer = new LinkedList();
        /**
         * 实时弹幕列表
         * @private @type {LinkedList}
         */
        let _realTimeBulletScreens = new LinkedList();
        /**
         * 延迟弹幕总数
         * @private @type {number}
         */
        let _delayBulletScreenCount = 0;
        /**
         * 延迟（单位：毫秒）
         * @private @type {number}
         */
        let _delay = 0;
        /**
         * 播放标志
         * @private @type {boolean}
         */
        let _playing;
        /**
         * requestAnimationFrame 句柄
         */
        let _requestAnimationFrameHandel = null;
        /**
         * 刷新频率
         * @private @type {number}
         */
        let _refreshRate = 0.06; //初始刷新频率
        /**
         * 上一次刷新时间
         * @private @type {number}
         */
        let _lastRefreshTime;
        /**
         * 全局选项
         * @private @type {openBSE~specialOptions}
         */
        let _options;
        /**
         * 默认全局变量
         * @private @readonly
         */
        const _defaultOptions = {
            /** 播放速度(倍数) */
            playSpeed: 1,
            /** 时间基准 */
            clock: time => performance_now() - _startTime,
            /** 缩放比例 */
            scaling: 1,
            /** 超时丢弃 */
            timeOutDiscard: true,
            /** 弹幕不透明度 */
            opacity: 1
        }

        /**
         * 全局选项类型
         * @private @readonly
         */
        const _optionsType = {
            playSpeed: 'number',
            clock: 'function',
            scaling: 'number',
            timeOutDiscard: 'boolean',
            opacity: 'number',
        }

        /**
         * 默认弹幕数据
         * @private @readonly
         */
        const _defaultBulletScreen = {
            /** 弹幕文本 */
            text: null,
            /** 是否允许丢弃 */
            canDiscard: true,
            /** 弹幕进入时间 */
            startTime: null,
            /** 弹幕退出时间 */
            endTime: null,
            /** 弹幕渲染代码 */
            renderCode: null
        }

        /** 
         * 默认弹幕样式 
         * @private @readonly
         * */
        const _defaultStyle = {
            /** 弹幕文本 */
            text: null,
            /** 阴影的模糊级别，0为不显示阴影 */
            shadowBlur: 2,
            /** 字体粗细 */
            fontWeight: '600',
            /** 字体系列 */
            fontFamily: 'sans-serif',
            /** 字体大小（单位：像素） */
            size: 25,
            /** 外框颜色 */
            boxColor: null,
            /** 字体颜色 */
            color: 'white',
            /** 描边颜色 */
            borderColor: null,
            /** 变换 */
            transform: null
        }

        /**
         * 弹幕数据类型
         * @private @readonly
         */
        const _bulletScreenType = {
            text: 'string',
            canDiscard: 'boolean',
            startTime: 'number',
            endTime: 'number',
            renderCode: 'string'
        }

        /** 
         * 默认弹幕样式数据类型 
         * @private @readonly
         * */
        const _defaultStyleType = {
            /** 弹幕文本 */
            text: 'string',
            /** 阴影的模糊级别，0为不显示阴影 */
            shadowBlur: 'number',
            /** 字体粗细 */
            fontWeight: ['string', 'number'],
            /** 字体系列 */
            fontFamily: 'string',
            /** 字体大小（单位：像素） */
            size: 'number',
            /** 外框颜色 */
            boxColor: ['string', 'null'],
            /** 字体颜色 */
            color: 'string',
            /** 描边颜色 */
            borderColor: ['string', 'null'],
            /** 变换 */
            transform: ['string', 'null']
        }

        _options = Helper.setValues(options, _defaultOptions, _optionsType); //设置默认值
        //初始化
        let _elementSize = {
            width: element.clientWidth / _options.scaling,
            height: element.clientHeight / _options.scaling
        }
        let _oldDevicePixelRatio = Helper.getDevicePixelRatio();
        let _oldScaling = _options.scaling;
        let _oldClientWidth = element.clientWidth;
        let _oldClientHeight = element.clientHeight;
        let _oldOpacity = _options.opacity;
        //渲染器工厂
        let renderersFactory = new RenderersFactory(element, _options, _elementSize);
        let _renderer = renderersFactory.getSpecialRenderer(renderMode); //实例化渲染器
        setInterval(setSize, 100);

        //公共函数
        /**
         * 设置或获取全局选项
         * @private
         **/
        Object.defineProperty(this, 'options', {
            get: function () {
                return Helper.clone(_options);
            },
            set: function (options) {
                _options = Helper.setValues(options, _options, _optionsType, false); //设置默认值
                if (_oldOpacity != _options.opacity) {
                    _oldOpacity = _options.opacity;
                    _renderer.setOpacity();
                }
            }
        });

        /**
         * 添加弹幕到弹幕列表。
         * @description 添加弹幕到弹幕列表。由于弹幕在屏幕上出现过后，弹幕引擎将从列表中彻底删除此弹幕。所以，在改变播放进度时，可能需要先[清空弹幕列表]{@link openBSE.BulletScreenEngine#cleanBulletScreenList}，然后重新加载此播放进度以后的弹幕。
         * @param {openBSE~SpecialBulletScreen} bulletScreen - 单条弹幕数据：一个 {@link openBSE~SpecialBulletScreen} 结构。
         * @throws {TypeError} 传入的参数错误时引发错误。请参阅 MDN [TypeError]{@link https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError} 。
         */
        this.add = function (bulletScreen) {
            _defaultBulletScreen.startTime = _options.clock();
            bulletScreen = Helper.setValues(bulletScreen, _defaultBulletScreen, _bulletScreenType); //设置默认值

            let newNode = new LinkedList.node(bulletScreen);
            _bulletScreenBuffer.forEach(function (node) {
                let lastBulletScreen = node.element;
                if (bulletScreen.startTime > lastBulletScreen.startTime) return {
                    add: { addToUp: true, node: newNode },
                    stop: true
                };
            }, true);
            if (newNode.linkedList === null) _bulletScreenBuffer.push(newNode, false);
        };

        /**
         * 开始播放弹幕。
         */
        this.play = function () {
            if (!_playing) {
                if (!_startTime)
                    _startTime = performance_now();
                if (_pauseTime)
                    _startTime += _options.clock() - _pauseTime;
                _lastRefreshTime = null;
                _playing = true;
                _requestAnimationFrameHandel = requestAnimationFrame(refresh);
            }
        };

        /**
         * 暂停播放弹幕。
         * @description 暂停播放弹幕。暂停播放弹幕是指弹幕播放暂停，所有未出现的弹幕将停止出现，已出现的弹幕停止运动，停止消失。
         */
        this.pause = function () {
            if (_playing) {
                _pauseTime = _options.clock();
                _playing = false;
                cancelAnimationFrame(_requestAnimationFrameHandel);
            }
        };

        /**
         * 清空弹幕缓冲区。
         * @description 清空弹幕列表，但屏幕上已经出现的弹幕不会被清除。
         */
        this.cleanBuffer = function () {
            _bulletScreenBuffer.clean();
        };

        /**
         * 清空屏幕内容。
         * @description 清空屏幕内容。清空屏幕上已经出现的弹幕，不包括弹幕列表中的弹幕。
         */
        this.cleanScreen = function () {
            _realTimeBulletScreens.clean();
            _renderer.cleanScreen();
        };

        /**
         * 停止播放弹幕。
         * @description 停止播放弹幕。停止播放弹幕是指停止播放弹幕，默认[时间基准（options.clock）]{@link openBSE~BulletScreenStyle}归零，并[清空弹幕列表]{@link openBSE.BulletScreenEngine#cleanBulletScreenList}、[清空屏幕内容]{@link openBSE.BulletScreenEngine#cleanScreen}。
         */
        this.stop = function () {
            if (_playing) {
                this.pause();
            }
            this.cleanBuffer();
            this.cleanScreen();
            _pauseTime = 0;
            _startTime = null;
        };

        /**
         * 获取或设置弹幕可见性。
         * @private
         */
        Object.defineProperty(this, 'visibility', {
            get: function () {
                return _renderer.getVisibility();
            },
            set: function (visibility) {
                if (visibility) _renderer.show();
                else _renderer.hide();
            }
        });

        /**
         * 获取渲染模式。
         * @private
         */
        Object.defineProperty(this, 'renderMode', {
            get: function () {
                return renderMode;
            }
        });

        /**
         * 获取播放状态。
         * @private
         */
        Object.defineProperty(this, 'playState', {
            get: function () {
                return _playing;
            }
        });

        /**
        * 获取调试信息。
        * @private
        */
        Object.defineProperty(this, 'debugInfo', {
            get: function () {
                return {
                    time: _playing ? _options.clock() : _pauseTime,
                    realTimeBulletScreenCount: _realTimeBulletScreens.length,
                    bufferBulletScreenCount: _bulletScreenBuffer.length,
                    delay: _delay,
                    delayBulletScreenCount: _delayBulletScreenCount,
                    fps: _playing ? Math.floor(_refreshRate * 1000) : 0 //帧频
                };
            }
        });

        //内部函数
        /**
         * 刷新弹幕函数
         * @private
         */
        function refresh(timestamp) {
            let nowTime = timestamp;
            if (_lastRefreshTime != null)
                _refreshRate = 1 / (nowTime - _lastRefreshTime);
            _lastRefreshTime = nowTime;
            addRealTimeBulletScreens();
            moveRealTimeBulletScreen();
            _renderer.draw(); //绘制弹幕
            if (_playing)
                _requestAnimationFrameHandel = requestAnimationFrame(refresh);
        }

        /**
         * 移动弹幕函数
         * @private
         */
        function moveRealTimeBulletScreen() {
            _realTimeBulletScreens.forEach((node) => {
                let realTimeBulletScreen = node.element;
                let nowTime = _options.clock();
                if (realTimeBulletScreen.bulletScreen.endTime > nowTime) {
                    try {
                        //创建解释器对象
                        let interpreter = new Interpreter(realTimeBulletScreen.bulletScreen.renderCode, (interpreter, scope) =>
                        InterpreterInit(interpreter, scope, realTimeBulletScreen));
                        interpreter.run();
                    }
                    catch(ex) {
                        console.error(ex);
                        _renderer.delete(realTimeBulletScreen);
                        return { remove: true };
                    }
                } else {
                    _renderer.delete(realTimeBulletScreen);
                    return { remove: true };
                }
            }, true);
        }

        /**
         * 添加弹幕到实时弹幕列表
         * @private
         */
        function addRealTimeBulletScreens() {
            if (_realTimeBulletScreens.length === 0)
                _delay = 0;
            let times = Math.floor(_refreshRate * 2000);
            do {
                let node = _bulletScreenBuffer.pop(false, false);
                if (node === null) return;
                let bulletScreen = node.element;
                let nowTime = _options.clock();
                if (bulletScreen.startTime > nowTime) return;
                if (!_options.timeOutDiscard || !bulletScreen.canDiscard || bulletScreen.startTime > nowTime - Math.floor(1 / _refreshRate) * 60) {
                    getRealTimeBulletScreen(bulletScreen);
                } else _delayBulletScreenCount++;
                node.remove();
                times--;
            } while (_realTimeBulletScreens.length === 0 || times > 0);
        }

        /**
         * 生成实时弹幕对象
         * @private
         * @param {openBSE~BulletScreen} bulletScreen - 弹幕的链表节点
         */
        function getRealTimeBulletScreen(bulletScreen) {
            let realTimeBulletScreen = {};
            realTimeBulletScreen.bulletScreen = bulletScreen;
            realTimeBulletScreen.style = Helper.clone(_defaultStyle);
            realTimeBulletScreen.style.text = bulletScreen.text;
            let newNode = new LinkedList.node(realTimeBulletScreen);
            _realTimeBulletScreens.push(newNode, false);
            _renderer.creat(realTimeBulletScreen); //创建弹幕元素
        }

        /**
         * 解释器加载
         * @private
         * @param {*} interpreter - 解释器对象
         * @param {*} scope - scope
         * @param {*} realTimeBulletScreen - 弹幕对象
         */
        function InterpreterInit(interpreter, scope, realTimeBulletScreen) {
            interpreter.setProperty(scope, 'time', _options.clock());
            interpreter.setProperty(scope, 'startTime', realTimeBulletScreen.startTime);
            interpreter.setProperty(scope, 'endTime', realTimeBulletScreen.endTime);
            interpreter.setProperty(scope, 'elementWidth', _elementSize.width);
            interpreter.setProperty(scope, 'elementHeight', _elementSize.height);
            interpreter.setProperty(scope, 'scaling', devicePixelRatio * options.scaling);
            interpreter.setProperty(scope, 'setStyle', interpreter.createNativeFunction((obj) => setStyle(obj.properties, realTimeBulletScreen)));
        }

        /**
         * 设置弹幕样式
         * @private
         * @param {*} style - 弹幕样式
         * @param {*} realTimeBulletScreen - 弹幕对象
         */
        function setStyle(style, realTimeBulletScreen) {
            if (Helper.isEmpty(style) || style === {}) return;
            let oldStyle = realTimeBulletScreen.style;
            realTimeBulletScreen.style = Helper.setValues(style, realTimeBulletScreen.style, _defaultStyleType, true);
            if (Helper.shallowEqual(oldStyle, realTimeBulletScreen.style)) return;
            _renderer.refresh(oldStyle, realTimeBulletScreen);
        }

        /**
         * 设置尺寸
         * @private
         */
        function setSize() {
            let devicePixelRatio = Helper.getDevicePixelRatio();
            if (_oldDevicePixelRatio != devicePixelRatio ||
                _oldClientWidth != element.clientWidth ||
                _oldClientHeight != element.clientHeight ||
                _oldScaling != _options.scaling) {
                _oldScaling = _options.scaling;
                _elementSize.width = element.clientWidth / _options.scaling;
                _elementSize.height = element.clientHeight / _options.scaling;
                _oldClientWidth = element.clientWidth;
                _oldClientHeight = element.clientHeight;
                _oldDevicePixelRatio = devicePixelRatio;
                _renderer.setSize();
                if (!_playing) _renderer.draw(); //非播放状态则重绘
            }
        }
    }
}

export default SpecialEngine</code></pre>
        </article>
    </section>




    
    
</div>

<br class="clear">

<footer>
    Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.5</a> on Mon Sep 28 2020 09:24:44 GMT+0000 (Coordinated Universal Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>

<script>prettyPrint();</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>

<script src="scripts/search.js" defer></script>


<script src="scripts/collapse.js" defer></script>


</body>
</html>
